package com.errol.generic.file.excel.service;

import com.renjia.service.generic.file.core.pojo.FileExtension;
import com.renjia.service.generic.file.core.service.FileService;
import com.renjia.service.generic.file.excel.model.ExcelDynamicColumn;
import com.renjia.service.generic.file.excel.pojo.ExcelColConfig;
import com.renjia.service.generic.file.excel.pojo.ExcelColumnDescriptor;
import com.renjia.service.generic.file.excel.pojo.ExcelConfig;
import org.springframework.stereotype.Service;
import sun.reflect.annotation.AnnotationParser;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by Errol on 20/04/24.
 *
 * @author zhang xiao bin
 * @version v1.0
 * @date 2020/10/21
 * @since v4.7.6
 */
@Service
class ExcelInnerServiceImpl {

    private final FileService fileService;
    private final List<FileExtension> excelExtensions = Arrays.asList(FileExtension.XLS, FileExtension.XLSX);

    public ExcelInnerServiceImpl(FileService fileService) {
        this.fileService = fileService;
    }

    List<FileExtension> getExcelExtensions() {
        return excelExtensions;
    }

    /**
     * 校验 ExcelConfig 注解
     */
    <R> void checkClassMapping(Class<R> clazz) {
        ExcelConfig excelConfig = clazz.getAnnotation(ExcelConfig.class);
        if (excelConfig == null) {
            throw new RuntimeException(clazz.toString() + " shall been annotated by @ExcelConfig");
        }
        if (!"".equals(excelConfig.filename()) && !excelExtensions.contains(fileService.getFileExtension(excelConfig.filename()))) {
            throw new RuntimeException("@ExcelConfig.filename shall been end with .xlsx or .xls");
        }
    }

    /**
     * 迭代解析 excel column config
     */
    <R> List<ExcelColumnDescriptor> getColumnDescriptors(Class<R> clazz, ExcelColumnDescriptor parentDescriptor) {
        return Arrays.stream(clazz.getDeclaredFields())
                .filter(field -> field.getAnnotation(ExcelColConfig.class) != null)
                .sorted(Comparator.comparingInt(field -> field.getAnnotation(ExcelColConfig.class).order()))
                .map(field -> {
                    ExcelColumnDescriptor descriptor = new ExcelColumnDescriptor(field, clazz, parentDescriptor);
                    if (isRecursive(field)) {
                        return getColumnDescriptors(field.getType(), descriptor);
                    } else {
                        return Collections.singletonList(descriptor);
                    }
                })
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
    }

    /**
     * 获取动态 excel 表头，动态 excel 只能用于导出
     */
    List<ExcelColumnDescriptor> getDynamicColumnDescriptors(List<ExcelDynamicColumn> header, ExcelColumnDescriptor parent) {
        return header.stream().map(excelDynamicColumn -> {
            Map<String, Object> config = new HashMap<>();
            config.put("title", excelDynamicColumn.getTitle());
            ExcelColConfig excelColConfig = (ExcelColConfig) AnnotationParser.annotationForMap(ExcelColConfig.class, config);
            ExcelColumnDescriptor child = new ExcelColumnDescriptor(excelColConfig, excelDynamicColumn.getValueType(), parent);
            if (excelDynamicColumn.getChildren() == null) {
                return Collections.singleton(child);
            } else {
                return getDynamicColumnDescriptors(excelDynamicColumn.getChildren(), child);
            }
        }).flatMap(Collection::stream).collect(Collectors.toList());
    }

    /**
     * 获取 excel 表头行数
     */
    int getSheetHeaderRowCount(List<ExcelColumnDescriptor> columnDescriptors) {
        OptionalInt max = columnDescriptors.stream().mapToInt(descriptor -> {
            int i = 1;
            ExcelColumnDescriptor child = descriptor.getChild();
            while (child != null) {
                i++;
                child = child.getChild();
            }
            return i;
        }).max();
        return max.isPresent() ? max.getAsInt() : -1;
    }

    /**
     * 判断 field 是否可以迭代
     */
    private boolean isRecursive(Field field) {
        String typeName = field.getGenericType().toString();
        if (typeName.startsWith("interface") || (typeName.contains("java.util") && !typeName.equals("class java.util.Date"))) {
            throw new RuntimeException("unsupported field type: " + typeName);
        }
        return typeName.startsWith("class") && !typeName.startsWith("class java") && !field.getType().isEnum();
    }
}
